package com.hdu.async_initialize_bean.processor;

import com.hdu.async_initialize_bean.annotation.AsyncInit;
import com.hdu.async_initialize_bean.holder.ApplicationContextHolder;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.Executor;

import static java.util.Optional.ofNullable;
import static org.springframework.util.ReflectionUtils.doWithMethods;


@Slf4j
public class AsyncInitBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        try {
            doWithMethods(bean.getClass(), method -> {
                ofNullable(method.getAnnotation(AsyncInit.class))
                        .ifPresent(asyncInit -> {
                            val executorsMap = ApplicationContextHolder.getBeansOfType(Executor.class);
                            val executor = executorsMap.get(asyncInit.executor());
                            val methodName = bean.getClass().getName() + "." + method.getName();
                            if (executor == null) {
                                log.warn("asyncInit error, methodName : {}, because executor not exist, executor : {}, " +
                                                "the initMethod will run by springRunner",
                                        methodName,
                                        asyncInit.executor()
                                );
                                // invoke by spring runner directly
                                try {
                                    method.invoke(bean);
                                } catch (IllegalAccessException | InvocationTargetException ignored) {

                                }
                            } else {
                                executor.execute(() -> {
                                    try {
                                        log.info("asyncInit start, methodName : {}", methodName);
                                        method.setAccessible(true);
                                        method.invoke(bean);
                                    } catch (Exception ignored) {

                                    }
                                });
                            }
                        });
            });
        } catch (Exception ignored) {
            // wont happen
        }
        return bean;
    }
}
